package com.mimo.common.configuration.log;

import java.text.MessageFormat;
import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mimo.common.http.header.HttpHeaderParams;
import com.mimo.common.result.BaseResult;
import com.mimo.common.utils.RandomStringUtils;

/**
 * 对web请求及回复添加日志跟踪.
 * <p>
 * 主要是对所有的Controller与RestController进行统一性拦截
 * <p>
 * <b> 特别提醒:该AOP用于处理日志打印, 会作用于所有业务接口的前后. 但对于入参只应该通过toString的方式用于打印.千万别搞什么JSON化,有可能在一些接口:
 * <li>字节数组
 * <li>JSON序列化时结构中的递归引用 都会导致接口失败. </br>
 * 如果真的需要, 则请在参数中用toString的方式去更好的描述自己. </b>
 */
@Aspect
public class WebLogAspect {

  private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

  private static final ThreadLocal<Long> elapse = ThreadLocal.withInitial(System::currentTimeMillis);

  private static final ThreadLocal<String> resquestId = ThreadLocal.withInitial(RandomStringUtils::uniqueRandom);

  @Autowired
  private ObjectMapper objMapper;

  @Pointcut("within(@org.springframework.stereotype.Controller *)  && !execution(* org.springframework.boot.autoconfigure.web.*.*(..))")
  private void controllerAsp() {
    // nothing need to do here.
  }

  @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
  private void restControllerAsp() {
    // nothing need to do here.
  }

  @Pointcut("controllerAsp() || restControllerAsp()")
  public void webLog() {
    // nothing need to do here.
  }

  @Before("webLog()")
  public void doBefore(JoinPoint joinPoint) {
    // 初始化起始计算时间
    elapse.get();

    // 接收到请求，记录请求内容
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    // to cater for some special situation, add the null point checking here.
    if (attributes == null) {
      return;
    }

    HttpServletRequest request = attributes.getRequest();

    StringBuilder sb = new StringBuilder();
    // 记录下请求内容
    sb.append("\r\n****************************Start[{0}]**************************************");
    sb.append("\r\n URL : {1}");
    sb.append("\r\n HTTP_METHOD : {2}");
    sb.append("\r\n IP : {3}");
    sb.append("\r\n CLASS_METHOD : {4}");
    sb.append("\r\n ARGS : {5}");
    sb.append("\r\n****************************End[{7}]**************************************");

    String info = MessageFormat.format(sb.toString(), resquestId.get(), request.getRequestURL().toString(),
        request.getMethod(), request.getHeader(HttpHeaderParams.CLIENT_IP),
        joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),
        Arrays.toString(joinPoint.getArgs()), resquestId.get());

    logger.info(info);

  }

  @AfterReturning(returning = "ret", pointcut = "webLog()")
  public void doAfterReturning(Object ret) throws JsonProcessingException {

    StringBuilder sb = new StringBuilder();
    sb.append("\r\n****************************Start[{0}]**************************************");
    sb.append("\r\nElapse time(ms) : {1}");
    sb.append("\r\nRESPONSE : {2}");
    sb.append("\r\n****************************End[{3}]**************************************");

    String info;

    if (logger.isDebugEnabled() && (ret instanceof BaseResult)) {
      info = MessageFormat.format(sb.toString(), resquestId.get(), System.currentTimeMillis() - elapse.get(),
          objMapper.writeValueAsString(ret), resquestId.get());
    } else {
      info = MessageFormat.format(sb.toString(), resquestId.get(), System.currentTimeMillis() - elapse.get(), ret,
          resquestId.get());

    }
    logger.info(info);

    elapse.remove();
    resquestId.remove();
  }
}